#include "form_self_check.h"
#include "ui_form_self_check.h"

// add necessary includes here

static QElapsedTimer g_etTimer;
#define DF_ELAPSED_SLEEP(tm) \
    g_etTimer.start(); \
    while(g_etTimer.elapsed() < tm) { QCoreApplication::processEvents(); QThread::msleep(100); }\

/*
 * Function:    FormSelfCheck
 * Description: The default constructor
 * Paramer:     void
 * Return:
 */
FormSelfCheck::FormSelfCheck(QWidget *parent) :
    QWidget(parent)
  , ui(new Ui::FormSelfCheck)
{
    qDebug() << Q_FUNC_INFO;
    ui->setupUi(this);
}

/*
 * Function:    ~FormSelfCheck
 * Description: The default destructor
 * Paramer:     void
 * Return:
 */
FormSelfCheck::~FormSelfCheck()
{
    qDebug() << Q_FUNC_INFO;
    delete ui;
}

/*
 * Function:    getInstance
 * Description: Get an instance of "FormSelfCheck" class, if exist, return it
 * Paramer:     QWidget *pParent - the pointer of QWidget class
 * Return:      the pointer of "FormSelfCheck" class
 */
FormSelfCheck* FormSelfCheck::getInstance(QWidget *pParent)
{
    static FormSelfCheck *pWidget = nullptr;
    if(!pWidget) {
        // Create a new object if it doesn't exist
        pWidget = new FormSelfCheck(pParent);
        if(nullptr == pWidget) return nullptr;

        // After constructing object, initialize the object
        pWidget->_initObj();

        // Init the window style
        pWidget->_initUi();
    }

    // Set the parent object
    if(pParent) pWidget->setParent(pParent);

    return pWidget;
}

/*
* Function:		changeEvent
* Description:	Triggers an event before it is switch language
* Paramer:		QShowEvent *e - the pointer of "QEvent"
* Return:		void
*/
void FormSelfCheck::changeEvent(QEvent *e)
{
    switch (e->type()) {
    case QEvent::LanguageChange:
        ui->retranslateUi(this);

        break;
    default:
        break;
    }

    QWidget::changeEvent(e);
}

/*
 * Function:    _initObj
 * Description: After constructing object, initialize the object
 * Paramer:     void
 * Return:      void
 */
void FormSelfCheck::_initObj()
{
    // Signal-slot initialization
    std::vector<bool> vecConnect;
    vecConnect.push_back(QObject::connect(this, SIGNAL(signalStartupSelfTest()), this, SLOT(slotStartupSelfTest()), static_cast<Qt::ConnectionType>(Qt::AutoConnection | Qt::UniqueConnection)));
    vecConnect.push_back(QObject::connect(this, SIGNAL(signalUpdateProgressBarValue(int, int, QString)), this, SLOT(slotUpdateProgressBarValue(int, int, QString)), static_cast<Qt::ConnectionType>(Qt::AutoConnection | Qt::UniqueConnection)));
    for(int i=0; i<(int)vecConnect.size(); i++) {
        Q_ASSERT(vecConnect.at(i));
    }
}

/*
* Function:		_initUi
* Description:	Init the window style
* Paramer:		void
* Return:		void
*/
void FormSelfCheck::_initUi()
{
    // Displays the background image of the window
    QWidget::setAttribute(Qt::WA_StyledBackground);

    QFile qssFile(QString(":/%1/home/%2.qss").arg("skin_1").arg(this->objectName()));
    if(qssFile.open(QFile::ReadOnly)) {
        this->setStyleSheet(qssFile.readAll());
        qssFile.close();
    }

    ui->progressBar_SelfTest->setValue(0);
    ui->progressBar_SelfTest->setTextVisible(false);

    ui->label_Inspection->setVisible(false);
    ui->label_Inspection->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
    ui->label_ProcessInfo->setAlignment(Qt::AlignHCenter | Qt::AlignBottom);

    ui->pushButton_SkipIt->setVisible(false);
}

void FormSelfCheck::on_pushButton_SkipIt_clicked()
{
    qDebug() << Q_FUNC_INFO;

    m_bSkipDetect = true;

    ui->pushButton_SkipIt->setVisible(false);
    emit signalUpdateProgressBarValue(0, 95, tr("Self-check are being cancelled"));
}

void FormSelfCheck::slotStartupSelfTest()
{
    qDebug() << Q_FUNC_INFO;

    QTimer::singleShot(DEF_SEC_INTERVAL, this, [&](){
        ui->label_Inspection->setVisible(true);
        ui->label_ProcessInfo->setVisible(true);
        ui->progressBar_SelfTest->setVisible(true);
    });

    emit signalUpdateProgressBarValue(0, 5, tr("Start self-check program"));
    DF_ELAPSED_SLEEP(500);

    QTimer::singleShot(5 * DEF_SEC_INTERVAL, this, [&](){
#ifdef __linux__
#ifdef QT_NO_DEBUG
        int nRetVal = _procDetectCAN(CanBLL_T1C::getInstance(this));
        if(0 != nRetVal) {
            if(0 > nRetVal) nRetVal = -1000 + nRetVal;

            // Tell manager the self-test completed
            emit signalSelfCheckCompleted(nRetVal);
        }
        else {
            // Waiting for cancelling less than 3 seconds
            ui->pushButton_SkipIt->setVisible(true);
            QTimer::singleShot(5 * DEF_SEC_INTERVAL, this, [&](){
                ui->pushButton_SkipIt->setVisible(false);

                if(!m_bSkipDetect) {
                    emit signalUpdateProgressBarValue(0, 8, tr("Reset motors"));

                    // Waiting for the motor-reset command less than 30 seconds
                    g_etTimer.start();
                    while(g_etTimer.elapsed() < 20 * DEF_SEC_INTERVAL && !m_bSkipDetect && !m_bMotorReseted) {
                        QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
                    }

                    if(!m_bSkipDetect) {
                        nRetVal = _procDetectAll();
                    }
                }
                else {
                    DF_ELAPSED_SLEEP(10 * 1000);

                    emit signalUpdateProgressBarValue(0, 97, tr("Reset motors") + " .");
                    DF_ELAPSED_SLEEP(100);

                    int nErrCode = CanBLL_T1C::getInstance(this)->doMethodDebug(CanVars::gs_nPipetteCanID_CH1, E_TP_SRST, QList<int>());
                    if(0 == nErrCode) {
                        emit signalUpdateProgressBarValue(0, 98, tr("Reset motors") + " ..");
                        DF_ELAPSED_SLEEP(100);

                        nErrCode = CanBLL_T1C::getInstance(this)->doMethodDebug(CanVars::gs_nIapCanID_CH1, E_TP_SRST, QList<int>());
                        if(0 == nErrCode) {
                            emit signalUpdateProgressBarValue(0, 100, tr("Self-check cancelled"));
                            DF_ELAPSED_SLEEP(100);
                        }
                    }
                }

                emit signalSelfCheckCompleted(nRetVal);
            });
        }
#else
        emit signalSelfCheckCompleted(0);
#endif
#else
        emit signalSelfCheckCompleted(0);
#endif
    });
}

void FormSelfCheck::slotUpdateProgressBarValue(int nRowIdx, int nValue, QString sInfo)
{
    qDebug() << Q_FUNC_INFO << nRowIdx << nValue << sInfo;

    if(0 <= nValue) {
        ui->progressBar_SelfTest->setValue(nValue);
        ui->label_ProcessInfo->setText(sInfo);
    }
}

/*
* Function:		_ProcDetectAll
* Description:	Deal with all device
* Paramer:		void
* Return:		void
*/
int FormSelfCheck::_procDetectAll()
{
    int nRetVal = 0;

    if(!m_bMotorReseted) {
        nRetVal = _procResetMotors(CanBLL_T1C::getInstance(this));
        if(0 != nRetVal) {
            if(0 > nRetVal) {
                nRetVal = -2000 + nRetVal;
                goto do_exit;
            }
            else {
                goto do_skit;
            }
        }
    }

    nRetVal = _procDetectPipette(CanBLL_T1C::getInstance(this));
    if(0 != nRetVal) {
        if(0 > nRetVal) {
            nRetVal = -3000 + nRetVal;
            goto do_exit;
        }
        else {
            goto do_skit;
        }
    }

    nRetVal = _procDetectMotion(CanBLL_T1C::getInstance(this));
    if(0 != nRetVal) {
        if(0 > nRetVal) {
            nRetVal = -4000 + nRetVal;
            goto do_exit;
        }
        else {
            goto do_skit;
        }
    }

    nRetVal = _procDetectHeating(CanBLL_T1C::getInstance(this));
    if(0 != nRetVal) {
        if(0 > nRetVal) {
            nRetVal = -5000 + nRetVal;
            goto do_exit;
        }
        else {
            goto do_skit;
        }
    }

    if(0 == nRetVal) {
        emit signalUpdateProgressBarValue(0, 100, tr("Self-check completed"));

        QTimer::singleShot(1000, this, [=]{
            // After completing the test, the buzzer sounds twice
            FirmwareBLL::getInstance(this)->debugBuzzer(1);
        });
    }
    else if(1 == nRetVal) {
do_skit:
        emit signalUpdateProgressBarValue(0, 100, tr("Self-check aborted"));
    }

do_exit:
    ui->pushButton_SkipIt->setVisible(false);
    ui->progressBar_SelfTest->setVisible(false);
    ui->label_ProcessInfo->setVisible(false);
    ui->label_Inspection->setVisible(false);

    qDebug() << Q_FUNC_INFO << nRetVal;
    return nRetVal;
}

/*
* Function:		_procDetectCAN
* Description:	Deal with detect CAN device
* Paramer:		CanBLL_T1C *pSerialbusBLL - the pointer of CanBLL_T1C structure
* Return:		int - the error code, 0:success; otherwise: failure
 */
int FormSelfCheck::_procDetectCAN(CanBLL_T1C *pSerialbusBLL)
{
    qDebug() << Q_FUNC_INFO;

    int nRetVal = 0;

    if(pSerialbusBLL) {
        STC_VERSION_INFO stVerInfo[3];

        do{
            emit signalUpdateProgressBarValue(0, 8, tr("Check the serialbus module"));
            DF_ELAPSED_SLEEP(100);

            // Select the firmware version info
            QList<unsigned int> lstCanID = QList<unsigned int>() << CanVars::gs_nIapCanID_CH1 << CanVars::gs_nPipetteCanID_CH1 << CanVars::gs_nHeatIapCanID_CH1;

            const int nListSize = 3;
            if(nListSize == lstCanID.size()) {
                for(int i=0; i<nListSize; i++) {
                    stVerInfo[i].clear();

                    int nErrCode = CanBLL_T1C::getInstance(this)->getIapVersion(stVerInfo[i], lstCanID.at(i));
                    if(0 == nErrCode) {
                        qDebug() << Q_FUNC_INFO << i << nErrCode << stVerInfo[i].sSerialNumber << stVerInfo[i].sFirmwareVer;
                    }

                    if(stVerInfo[i].sFirmwareVer.trimmed().isEmpty()) {
                        nRetVal = - (i+1);
                        goto do_exit;
                    }
                }
            }

        }while(0);

        if(0 == nRetVal) {
            emit signalUpdateProgressBarValue(0, 10, tr("The serialbus module is normal"));
        }
        else {
do_exit:
            emit signalUpdateProgressBarValue(0, 10, tr("The serialbus module is abnormal"));
        }
    }

    qDebug() << Q_FUNC_INFO << nRetVal;
    return nRetVal;
}

/*
* Function:		_procResetMotors
* Description:	Deal with reset all motors
* Paramer:		CanBLL_T4 *pSerialbusBLL - the pointer of CanBLL_T4 structure
* Return:		int - the error code, 0:success; otherwise: failure
*/
int FormSelfCheck::_procResetMotors(CanBLL_T1C *pSerialbusBLL)
{
    qDebug() << Q_FUNC_INFO;

    int nRetVal = 0;

    if(pSerialbusBLL) {
        do{
            emit signalUpdateProgressBarValue(0, 15, tr("Reset motors") + " .");
            DF_ELAPSED_SLEEP(100);
            nRetVal = pSerialbusBLL->doMethodDebug(CanVars::gs_nPipetteCanID_CH1, E_TP_SRST, QList<int>());
            if(0 != nRetVal) {
                nRetVal = -1;
                goto do_exit;
            }

            emit signalUpdateProgressBarValue(0, 18, tr("Reset motors") + " ..");
            DF_ELAPSED_SLEEP(100);
            nRetVal = pSerialbusBLL->doMethodDebug(CanVars::gs_nIapCanID_CH1, E_TP_SRST, QList<int>());
            if(0 != nRetVal) {
                nRetVal = -2;
                goto do_exit;
            }
        }while(0);

        if(0 == nRetVal) {
            emit signalUpdateProgressBarValue(0, 20, tr("Motors is normal"));
        }
        else {
do_exit:
            emit signalUpdateProgressBarValue(0, 20, tr("Motors is abnormal"));
        }
    }

    qDebug() << Q_FUNC_INFO << nRetVal;
    return nRetVal;
}

/*
* Function:		_procDetectPipette
* Description:	Deal with detect pipette device
* Paramer:		CanBLL_T1C *pSerialbusBLL - the pointer of CanBLL_T1C structure
* Return:		int - the error code, 0:success; otherwise: failure
*/
int FormSelfCheck::_procDetectPipette(CanBLL_T1C *pSerialbusBLL)
{
    qDebug() << Q_FUNC_INFO;

    int nRetVal = 0;

    if(pSerialbusBLL) {
        do{
            emit signalUpdateProgressBarValue(0, 25, tr("Check the pipette module"));
            DF_ELAPSED_SLEEP(100);
            // Step 1: Exit cartridge
            nRetVal = pSerialbusBLL->doMethodDebug(CanVars::gs_nIapCanID_CH1, E_TP_QUIT, QList<int>() << MotorVars_T1C::nGrapTipsOffsetX);
            if(0 != nRetVal) {
                nRetVal = -1;
                goto do_exit_2;
            }

            emit signalUpdateProgressBarValue(0, 30, tr("Check the pipette module") + " .");
            DF_ELAPSED_SLEEP(100);
            // Step 2: Move downwards at the bottom of tipspipe
            nRetVal = pSerialbusBLL->doMethodDebug(CanVars::gs_nPipetteCanID_CH1, E_PA_ABS_MOVE, QList<int>() << MotorVars_T1C::nDiscardTipsOffsetY);
            if(0 != nRetVal) {
                nRetVal = -2;
                goto do_exit;
            }

            emit signalUpdateProgressBarValue(0, 35, tr("Check the pipette module") + " ..");
            DF_ELAPSED_SLEEP(100);
            // Step 3: Move downwards at a vertical reserve space (Discard the tipspile)
            nRetVal = pSerialbusBLL->doMethodDebug(CanVars::gs_nPipetteCanID_CH1, E_PA_SPIT, QList<int>() << MotorVars_T1C::nTipsMaxVolume + MotorVars_T1C::nPierceRemainOffsetY);
            if(0 != nRetVal) {
                nRetVal = -3;
                goto do_exit;
            }

            emit signalUpdateProgressBarValue(0, 40, tr("Check the pipette module") + " ...");
            DF_ELAPSED_SLEEP(100);
            // Step 4: Move upwards at a vertical reserve space (Restore piercing head)
            nRetVal = pSerialbusBLL->doMethodDebug(CanVars::gs_nPipetteCanID_CH1, E_PA_SUCTION, QList<int>() << MotorVars_T1C::nTipsMaxVolume + MotorVars_T1C::nPierceRemainOffsetY);
            if(0 != nRetVal) {
                nRetVal = -4;
                goto do_exit;
            }

            emit signalUpdateProgressBarValue(0, 45, tr("Check the pipette module") + " ....");
            DF_ELAPSED_SLEEP(100);
            // Step 5: Move upwards
            nRetVal = pSerialbusBLL->doMethodDebug(CanVars::gs_nPipetteCanID_CH1, E_PA_ABS_MOVE, QList<int>() << MotorVars_T1C::nResetTipsOffsetY2);
            if(0 != nRetVal) {
                nRetVal = -5;
                goto do_exit;
            }

            emit signalUpdateProgressBarValue(0, 48, tr("Check the pipette module") + " .....");
            DF_ELAPSED_SLEEP(100);
            // Step 6: Load cartridge
            nRetVal = pSerialbusBLL->doMethodDebug(CanVars::gs_nIapCanID_CH1, E_TP_LOAD, QList<int>() << MotorVars_T1C::nGrapTipsOffsetX);
            if(0 != nRetVal) {
                nRetVal = -6;
                goto do_exit_2;
            }
        }while(0);

        if(0 == nRetVal) {
            emit signalUpdateProgressBarValue(0, 50, tr("The pipette module is normal"));
        }
        else {
do_exit:
            // Reset pipetting motors
            pSerialbusBLL->doMethodDebug(CanVars::gs_nPipetteCanID_CH1, E_TP_SRST, QList<int>());
do_exit_2:
            // Reset tray motors
            pSerialbusBLL->doMethodDebug(CanVars::gs_nIapCanID_CH1, E_TP_SRST, QList<int>());

            emit signalUpdateProgressBarValue(0, 50, tr("The pipette module is abnormal"));
        }
    }

    DF_ELAPSED_SLEEP(100);
    qDebug() << Q_FUNC_INFO << nRetVal;
    return nRetVal;
}

/*
* Function:		_procDetectMotion
* Description:	Deal with detect motor device
* Paramer:		CanBLL_T1C *pSerialbusBLL - the pointer of CanBLL_T1C structure
* Return:		int - the error code, 0:success; otherwise: failure
*/
int FormSelfCheck::_procDetectMotion(CanBLL_T1C *pSerialbusBLL)
{
    qDebug() << Q_FUNC_INFO;

    int nRetVal = 0;

    if(pSerialbusBLL) {
        do{
            emit signalUpdateProgressBarValue(0, 55, tr("Check the motion module"));
            DF_ELAPSED_SLEEP(100);
            // Step 1: Exit cartridge
            nRetVal = pSerialbusBLL->doMethodDebug(CanVars::gs_nIapCanID_CH1, E_TP_QUIT, QList<int>() << MotorVars_T1C::nGrapSleeveOffsetX);
            if(0 != nRetVal) {
                nRetVal = -1;
                goto do_exit;
            }

            emit signalUpdateProgressBarValue(0, 60, tr("Check the motion module") + " .");
            DF_ELAPSED_SLEEP(100);
            // Step 2: Magnetic sleeve magnetic rod down
            nRetVal = pSerialbusBLL->doMethodDebug(CanVars::gs_nIapCanID_CH1, E_TP_LKDO, QList<int>() << MotorVars_T1C::nMixingSleeveOffsetY);
            if(0 != nRetVal) {
                nRetVal = -2;
                goto do_exit;
            }

            emit signalUpdateProgressBarValue(0, 65, tr("Check the motion module") + " ..");
            DF_ELAPSED_SLEEP(100);
            // Step 3: Magnetic sleeve up
            nRetVal = pSerialbusBLL->doMethodDebug(CanVars::gs_nIapCanID_CH1, E_TP_SLUP, QList<int>() << MotorVars_T1C::nDiscardSleeveOffsetY);
            if(0 != nRetVal) {
                nRetVal = -3;
                goto do_exit;
            }

            emit signalUpdateProgressBarValue(0, 70, tr("Check the motion module") + " ...");
            DF_ELAPSED_SLEEP(100);
            // Step 4: Magnetic rod motor up
            nRetVal = pSerialbusBLL->doMethodDebug(CanVars::gs_nIapCanID_CH1, E_TP_SKUP, QList<int>() << MotorVars_T1C::nMixingSleeveOffsetY);
            if(0 != nRetVal) {
                nRetVal = -4;
                goto do_exit;
            }

            emit signalUpdateProgressBarValue(0, 75, tr("Check the motion module") + " ....");
            DF_ELAPSED_SLEEP(100);
            // Step 5: Magnetic sleeve up
            nRetVal = pSerialbusBLL->doMethodDebug(CanVars::gs_nIapCanID_CH1, E_TP_SLUP, QList<int>() << MotorVars_T1C::nMixingSleeveOffsetY - MotorVars_T1C::nDiscardSleeveOffsetY);
            if(0 != nRetVal) {
                nRetVal = -5;
                goto do_exit;
            }

            emit signalUpdateProgressBarValue(0, 80, tr("Check the motion module") + " .....");
            DF_ELAPSED_SLEEP(100);
            // Step 6: Load cartridge
            nRetVal = pSerialbusBLL->doMethodDebug(CanVars::gs_nIapCanID_CH1, E_TP_LOAD, QList<int>() << MotorVars_T1C::nGrapSleeveOffsetX);
            if(0 != nRetVal) {
                nRetVal = -6;
                goto do_exit;
            }
        }while(0);

        if(0 == nRetVal) {
            emit signalUpdateProgressBarValue(0, 85, tr("The motion module is normal"));
        }
        else {
do_exit:
            nRetVal = -1;

            // Reset tray motors
            pSerialbusBLL->doMethodDebug(CanVars::gs_nIapCanID_CH1, E_TP_SRST, QList<int>());

            emit signalUpdateProgressBarValue(0, 85, tr("The motion module is abnormal"));
        }
    }

    DF_ELAPSED_SLEEP(100);
    qDebug() << Q_FUNC_INFO << nRetVal;
    return nRetVal;
}

/*
* Function:		_procDetectHeating
* Description:	Deal with detect heating device
* Paramer:		CanBLL_T1C *pSerialbusBLL - the pointer of CanBLL_T1C structure
* Return:		int - the error code, 0:success; otherwise: failure
*/
int FormSelfCheck::_procDetectHeating(CanBLL_T1C *pSerialbusBLL)
{
    qDebug() << Q_FUNC_INFO;

    int nRetVal = 0;
    QList<float> lstTempVal;

    if(pSerialbusBLL) {
        do{
            emit signalUpdateProgressBarValue(0, 90, tr("Check the heating module"));
            DF_ELAPSED_SLEEP(100);
            // Step 1: Disable heat on channels
            nRetVal = CanBLL_T1C::getInstance(this)->doMethodDebug(CanVars::gs_nHeatIapCanID_CH1, HT_HA_HTSP, QList<int>());
            if(0 != nRetVal) {
                nRetVal = -1;
                goto do_exit;
            }

            emit signalUpdateProgressBarValue(0, 92, tr("Check the heating module") + " .");
            DF_ELAPSED_SLEEP(100);
            // Step 2: Read temperature again on channels
            nRetVal = CanBLL_T1C::getInstance(this)->getChannelTemperature(lstTempVal);
            if(0 != nRetVal) {
                if(DEF_HEAT_CHANNEL_SIZE + 1 == lstTempVal.size()) {
                    nRetVal = -10;
                    int nErrCH = 0x0;
                    for(int i=0; i<DEF_HEAT_CHANNEL_SIZE; i++) {
                        if(10 > lstTempVal[i + 1]) {
                            nErrCH |= (0x1 << i);
                        }
                    }

                    if(0 < nErrCH) {
                        nRetVal += -nErrCH;
                    }
                }
                else {
                    nRetVal = -2;
                }

                goto do_exit;
            }
        }while(0);

        if(0 == nRetVal) {
            emit signalUpdateProgressBarValue(0, 95, tr("The heating module is normal"));
        }
        else {
do_exit:
            emit signalUpdateProgressBarValue(0, 95, tr("The heating module is abnormal"));
        }
    }

    DF_ELAPSED_SLEEP(100);
    qDebug() << Q_FUNC_INFO << nRetVal << lstTempVal;
    return nRetVal;
}
